package com.deviceplug;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public final class CUSBListener {
    private static final boolean DEBUG = false;

    private static final String TAG = "USBListener";

    private static final String ACTION_USB_PERMISSION_BASE = "com.spca.USB_PERMISSION.";

    private final String ACTION_USB_PERMISSION = "com.spca.USB_PERMISSION." + hashCode();

    public static final String ACTION_USB_DEVICE_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED";

    private final ConcurrentHashMap<UsbDevice, UsbControlBlock> mCtrlBlocks = new ConcurrentHashMap<>();

    private final WeakReference<Context> mWeakContext;

    private final UsbManager mUsbManager;

    private final OnDeviceConnectListener mOnDeviceConnectListener;

    private PendingIntent mPermissionIntent = null;

    private List<CDeviceFilter> mDeviceFilters = new ArrayList<>();

    private final Handler mHandler = new Handler();

    private final BroadcastReceiver mUsbReceiver;

    private volatile int mDeviceCounts;

    private final Runnable mDeviceCheckRunnable;

    public void destroy() {
        unregister();
        Set<UsbDevice> keys = this.mCtrlBlocks.keySet();
        if (keys != null) {
            try {
                for (UsbDevice key : keys) {
                    UsbControlBlock ctrlBlock = this.mCtrlBlocks.remove(key);
                    ctrlBlock.close();
                }
            } catch (Exception e) {
                Log.e("USBListener", "destroy:", e);
            }
            this.mCtrlBlocks.clear();
        }
    }

    public synchronized void register() {
        if (this.mPermissionIntent == null) {
            Context context = this.mWeakContext.get();
            if (context != null) {
                this.mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(this.ACTION_USB_PERMISSION), 0);
                IntentFilter filter = new IntentFilter(this.ACTION_USB_PERMISSION);
                //filter.addAction("android.hardware.usb.action.USB_DEVICE_ATTACHED");
                filter.addAction("android.hardware.usb.action.USB_DEVICE_DETACHED");
                context.registerReceiver(this.mUsbReceiver, filter);
            }
            this.mDeviceCounts = 0;
            this.mHandler.postDelayed(this.mDeviceCheckRunnable, 1000L);
        }
    }

    public synchronized void unregister() {
        if (this.mPermissionIntent != null) {
            Context context = this.mWeakContext.get();
            if (context != null)
                context.unregisterReceiver(this.mUsbReceiver);
            this.mPermissionIntent = null;
        }
        this.mDeviceCounts = 0;
        this.mHandler.removeCallbacks(this.mDeviceCheckRunnable);
    }

    public synchronized boolean isRegistered() {
        return (this.mPermissionIntent != null);
    }

    public void setDeviceFilter(CDeviceFilter filter) {
        this.mDeviceFilters.clear();
        this.mDeviceFilters.add(filter);
    }

    public void setDeviceFilter(List<CDeviceFilter> filters) {
        this.mDeviceFilters.clear();
        this.mDeviceFilters.addAll(filters);
    }

    public int getDeviceCount() {
        return getDeviceList().size();
    }

    public List<UsbDevice> getDeviceList() {
        return getDeviceList(this.mDeviceFilters);
    }

    public List<UsbDevice> getDeviceList(List<CDeviceFilter> filters) {
        HashMap<String, UsbDevice> deviceList = this.mUsbManager.getDeviceList();
        List<UsbDevice> result = new ArrayList<>();
        if (deviceList != null)
            if (filters == null || filters.isEmpty()) {
                result.addAll(deviceList.values());
            } else {
                Iterator<UsbDevice> iterator = deviceList.values().iterator();
                while (iterator.hasNext()) {
                    UsbDevice device = iterator.next();
                    label19: for (CDeviceFilter filter : filters) {
                        if (filter != null) {
                            if (filter.matches(device))
                                break label19;
                            continue;
                        }
                        result.add(device);
                    }
                }
            }
        return result;
    }

    public List<UsbDevice> getDeviceList(CDeviceFilter filter) {
        HashMap<String, UsbDevice> deviceList = this.mUsbManager.getDeviceList();
        List<UsbDevice> result = new ArrayList<>();
        if (deviceList != null) {
            Iterator<UsbDevice> iterator = deviceList.values().iterator();
            while (iterator.hasNext()) {
                UsbDevice device = iterator.next();
                if (filter == null || filter.matches(device))
                    result.add(device);
            }
        }
        return result;
    }

    public Iterator<UsbDevice> getDevices() {
        Iterator<UsbDevice> iterator = null;
        HashMap<String, UsbDevice> list = this.mUsbManager.getDeviceList();
        if (list != null)
            iterator = list.values().iterator();
        return iterator;
    }

    public boolean hasPermission(UsbDevice device) {
        return (device != null && this.mUsbManager.hasPermission(device));
    }

    public synchronized void requestPermission(UsbDevice device) {
        if (this.mPermissionIntent != null) {
            if (device != null) {
                if (this.mUsbManager.hasPermission(device)) {
                    procConnect(device);
                } else {
                    this.mUsbManager.requestPermission(device, this.mPermissionIntent);
                }
            } else {
                procCancel(device);
            }
        } else {
            procCancel(device);
        }
    }

    public CUSBListener(Context context, OnDeviceConnectListener listener) {
        this.mUsbReceiver = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (CUSBListener.this.ACTION_USB_PERMISSION.equals(action)) {
                    synchronized (CUSBListener.this) {
                        UsbDevice device = (UsbDevice)intent.getParcelableExtra("device");
                        if (intent.getBooleanExtra("permission", false)) {
                            if (device != null)
                                CUSBListener.this.procConnect(device);
                        } else {
                            CUSBListener.this.procCancel(device);
                        }
                    }
                } else if ("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(action)) {
                    UsbDevice device = (UsbDevice)intent.getParcelableExtra("device");
                    CUSBListener.this.procAttach(device);
                } else if ("android.hardware.usb.action.USB_DEVICE_DETACHED".equals(action)) {
                    UsbDevice device = (UsbDevice)intent.getParcelableExtra("device");
                    if (device != null) {
                        UsbControlBlock ctrlBlock = null;
                        ctrlBlock = (UsbControlBlock)CUSBListener.this.mCtrlBlocks.remove(device);
                        if (ctrlBlock != null)
                            ctrlBlock.close();
                        CUSBListener.this.mDeviceCounts = 0;
                        CUSBListener.this.procDettach(device);
                    }
                }
            }
        };
        this.mDeviceCounts = 0;
        this.mDeviceCheckRunnable = new Runnable() {
            public void run() {
                int n = CUSBListener.this.getDeviceCount();
                if (n != CUSBListener.this.mDeviceCounts &&
                        n > CUSBListener.this.mDeviceCounts) {
                    CUSBListener.this.mDeviceCounts = n;
                    if (CUSBListener.this.mOnDeviceConnectListener != null)
                        CUSBListener.this.mOnDeviceConnectListener.onAttach(null);
                }
                CUSBListener.this.mHandler.postDelayed(this, 2000L);
            }
        };
        this.mWeakContext = new WeakReference<>(context);
        this.mUsbManager = (UsbManager)context.getSystemService("usb");
        this.mOnDeviceConnectListener = listener;
    }

    private final void procConnect(final UsbDevice device) {
        this.mHandler.post(new Runnable() {
            public void run() {
                boolean createNew;
                UsbControlBlock ctrlBlock = (UsbControlBlock)CUSBListener.this.mCtrlBlocks.get(device);
                if (ctrlBlock == null) {
                    ctrlBlock = new UsbControlBlock(CUSBListener.this, device);
                    CUSBListener.this.mCtrlBlocks.put(device, ctrlBlock);
                    createNew = true;
                } else {
                    createNew = false;
                }
                if (CUSBListener.this.mOnDeviceConnectListener != null) {
                    UsbControlBlock ctrlB = ctrlBlock;
                    CUSBListener.this.mOnDeviceConnectListener.onConnect(device, ctrlB, createNew);
                }
            }
        });
    }

    private final void procCancel(UsbDevice device) {
        if (this.mOnDeviceConnectListener != null)
            this.mHandler.post(new Runnable() {
                public void run() {
                    CUSBListener.this.mOnDeviceConnectListener.onCancel();
                }
            });
    }

    private final void procAttach(final UsbDevice device) {
        if (this.mOnDeviceConnectListener != null)
            this.mHandler.post(new Runnable() {
                public void run() {
                    CUSBListener.this.mOnDeviceConnectListener.onAttach(device);
                }
            });
    }

    private final void procDettach(final UsbDevice device) {
        if (this.mOnDeviceConnectListener != null)
            this.mHandler.post(new Runnable() {
                public void run() {
                    CUSBListener.this.mOnDeviceConnectListener.onDettach(device);
                }
            });
    }

    public static interface OnDeviceConnectListener {
        void onAttach(UsbDevice param1UsbDevice);

        void onDettach(UsbDevice param1UsbDevice);

        void onConnect(UsbDevice param1UsbDevice, UsbControlBlock param1UsbControlBlock, boolean param1Boolean);

        void onDisconnect(UsbDevice param1UsbDevice, UsbControlBlock param1UsbControlBlock);

        void onCancel();
    }

    public static final class UsbControlBlock {
        private final WeakReference<CUSBListener> mWeakMonitor;

        private final WeakReference<UsbDevice> mWeakDevice;

        protected UsbDeviceConnection mConnection;

        private final int mBusNum;

        private final int mDevNum;

        private final SparseArray<UsbInterface> mInterfaces = new SparseArray();

        public UsbControlBlock(CUSBListener listener, UsbDevice device) {
            this.mWeakMonitor = new WeakReference<>(listener);
            this.mWeakDevice = new WeakReference<>(device);
            this.mConnection = listener.mUsbManager.openDevice(device);
            String name = device.getDeviceName();
            String[] v = !TextUtils.isEmpty(name) ? name.split("/") : null;
            int busnum = 0;
            int devnum = 0;
            if (v != null) {
                busnum = Integer.parseInt(v[v.length - 2]);
                devnum = Integer.parseInt(v[v.length - 1]);
            }
            this.mBusNum = busnum;
            this.mDevNum = devnum;
            if (this.mConnection == null)
                Log.e("USBListener", "could not connect to device " + name);
        }

        public UsbDevice getDevice() {
            return this.mWeakDevice.get();
        }

        public String getDeviceName() {
            UsbDevice device = this.mWeakDevice.get();
            return (device != null) ? device.getDeviceName() : "";
        }

        public UsbDeviceConnection getUsbDeviceConnection() {
            return this.mConnection;
        }

        public synchronized int getFileDescriptor() {
            return (this.mConnection != null) ? this.mConnection.getFileDescriptor() : -1;
        }

        public byte[] getRawDescriptors() {
            return (this.mConnection != null) ? this.mConnection.getRawDescriptors() : null;
        }

        public int getVenderId() {
            UsbDevice device = this.mWeakDevice.get();
            return (device != null) ? device.getVendorId() : 0;
        }

        public int getProductId() {
            UsbDevice device = this.mWeakDevice.get();
            return (device != null) ? device.getProductId() : 0;
        }

        public int getBcdDevice() {
            byte[] descriptor = this.mConnection.getRawDescriptors();
            int bcd = descriptor[13] << 8 | descriptor[12];
            return bcd;
        }

        public synchronized String getSerial() {
            return (this.mConnection != null) ? this.mConnection.getSerial() : null;
        }

        public int getBusNum() {
            return this.mBusNum;
        }

        public int getDevNum() {
            return this.mDevNum;
        }

        public synchronized UsbInterface open(int interfaceIndex) {
            UsbDevice device = this.mWeakDevice.get();
            UsbInterface intf = null;
            intf = (UsbInterface)this.mInterfaces.get(interfaceIndex);
            if (intf == null) {
                intf = device.getInterface(interfaceIndex);
                if (intf != null)
                    synchronized (this.mInterfaces) {
                        this.mInterfaces.append(interfaceIndex, intf);
                    }
            }
            return intf;
        }

        public void close(int interfaceIndex) {
            UsbInterface intf = null;
            synchronized (this.mInterfaces) {
                intf = (UsbInterface)this.mInterfaces.get(interfaceIndex);
                if (intf != null) {
                    this.mInterfaces.delete(interfaceIndex);
                    this.mConnection.releaseInterface(intf);
                }
            }
        }

        public synchronized void close() {
            if (this.mConnection != null) {
                int n = this.mInterfaces.size();
                for (int i = 0; i < n; i++) {
                    int key = this.mInterfaces.keyAt(i);
                    UsbInterface intf = (UsbInterface)this.mInterfaces.get(key);
                    this.mConnection.releaseInterface(intf);
                }
                this.mConnection.close();
                this.mConnection = null;
                CUSBListener listener = this.mWeakMonitor.get();
                if (listener != null) {
                    if (listener.mOnDeviceConnectListener != null) {
                        UsbDevice device = this.mWeakDevice.get();
                        listener.mOnDeviceConnectListener.onDisconnect(device, this);
                    }
                    listener.mCtrlBlocks.remove(getDevice());
                }
            }
        }
    }
}
